package dark

import (
	"errors"
	"io/fs"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

const (
	O_APPEND_WRITE = os.O_CREATE | os.O_WRONLY | os.O_APPEND
	O_NEW_WRITE    = os.O_CREATE | os.O_WRONLY | os.O_TRUNC
	O_READ_ONLY    = os.O_WRONLY
)

var (
	_home, _ = os.UserHomeDir()
	_tmp     = os.TempDir()
	HOME     = Str(_home)
	TMP      = Str(_tmp)
)

func (str Str) MustAsFile() Str {

	buf, err := ioutil.ReadFile(str.String())
	if err != nil {
		panic(err)
	}
	return Str(buf)
}

func (str Str) Exists() Str {
	_, err := os.Stat(str.String())
	if err == nil {
		return str
	} else {
		log.Fatal(Str(str+"not found this file!").Color("r", "B"))
		return str
	}
}

func (str Str) Replace(l string, n string) Str {
	return Str(strings.ReplaceAll(str.String(), l, n))
}

/*
ReplaceRe
ReplaceRe(`\W(\w+)\W`, 'hello') ->  \W(hello)\W
*/
func (str Str) ReplaceRe(l string, n string) Str {
	e := regexp.MustCompile(l)
	if S(l).In("(") && S(l).In(")") {
		buf := str
		for _, vv := range e.FindAllStringSubmatch(str.String(), -1) {

			// fmt.Println(strings.Join(vv[1:], "||"))
			allFinded := vv[0]
			hit := vv[1]
			newReplaceed := S(allFinded).Replace(hit, n)
			// fmt.Println(allFinded, "-->", newReplaceed)
			buf = buf.Replace(allFinded, newReplaceed.Str())
		}
		return buf
	} else {
		return Str(e.ReplaceAll(str.Bytes(), []byte(n)))
	}
}

func (str Str) ExpandUser() Str {
	home, err := os.UserHomeDir()
	if err != nil {
		panic(err)
	}
	return str.Replace("~", home)
}

func (str Str) PathJoin(sub ...string) Str {
	e := str.String()
	for _, i := range sub {
		e = filepath.Join(e, i)
	}
	return Str(e)
}

func (str Str) IsPath() Str {
	_, err := os.Stat(str.String())
	if err == nil {
		return str
	}
	str.Color("r", "R").Println("not path")
	return ""
}

func (str Str) Dir() Str {
	f, err := os.Stat(str.String())
	if err == nil && f.IsDir() {
		return str
	} else {
		log.Fatal(Str(str+"not found this dir!").Color("r", "B"))
		return str
	}

}

func (str Str) Basename() Str {
	return Str(filepath.Base(str.String()))
}

func (str Str) Dirname() Str {
	return Str(filepath.Dir(str.String()))
}

func (str Str) IsDir() bool {
	f, err := os.Stat(str.String())
	return err == nil && f.IsDir()

}

func (str Str) Mkdir() bool {
	if str.IsDir() {
		return true
	}
	err := os.MkdirAll(str.String(), os.ModePerm)
	if err != nil {
		return str.IsDir()
	} else {
		return true
	}
}

func (str Str) IsExists() bool {
	_, err := os.Stat(str.String())
	return err == nil

}

func (str Str) Walk(w func(path Str, isDir bool, err error) error) {
	filepath.Walk(str.ExpandUser().String(), func(path string, info fs.FileInfo, err error) error {
		if info == nil {
			return w(Str(path), false, err)
		}
		return w(Str(path), info.IsDir(), err)
	})
}

func (str Str) CRLF2LF() Str {

	return str.Replace("\r\n", "\n")
}

func (str Str) OpenFile(flag ...int) (nowSize int64, fp *os.File, err error) {

	fs, err := os.Stat(str.Str())

	f := os.O_CREATE
	if err == nil {
		nowSize = fs.Size()
		f |= O_APPEND_WRITE

	} else {
		f = O_NEW_WRITE
	}

	if flag != nil {
		f = 0
		for _, i := range flag {
			f |= i
		}
	}
	fp, err = os.OpenFile(str.Str(), f, os.ModePerm)
	return
}

func (str Str) ToFile(path string, flag ...int) error {
	f := os.O_CREATE
	if flag != nil {
		for _, i := range flag {
			f |= i
		}
	} else {
		f |= O_APPEND_WRITE
	}
	fp, err := os.OpenFile(path, f, os.ModePerm)
	if err != nil {
		return err
	}
	defer fp.Close()
	fp.WriteString(str.String())
	return nil
}

func (str Str) AbsPath() Str {
	a, err := filepath.Abs(str.Str())
	if err != nil {
		panic(err)
	}
	return Str(a)
}

func (str Str) Cp(newpath string, filterFunc ...func(fpath Str, isDir bool) bool) error {
	if str.IsPath() != "" {
		str := str.AbsPath()
		if !str.IsDir() {
			if filterFunc != nil {
				if !filterFunc[0](str, false) {
					return nil
				}
			}
			if fbuf := str.MustAsFile(); fbuf != "" {
				if err := fbuf.ToFile(newpath, O_NEW_WRITE); err != nil {
					return err
				}
				return nil
			}
			if str.IsExists() && !str.IsDir() {
				return nil
			}
			return errors.New("this is not file :" + str.Str())
		} else {
			if Str(newpath).Mkdir() {
				dstDir := Str(newpath)
				if Str(newpath).EndsWith(PathSep) {
					dstDir = dstDir.PathJoin(str.Basename().Str())
				}
				srcBase := str.Dirname()
				if !str.EndsWith(PathSep) {
					srcBase = str
				}
				// srcBase.Color("g").Println("sb")
				// dstDir.Color("y").Println("db")
				str.Walk(func(path Str, isDir bool, err error) error {
					if filterFunc != nil {
						if !filterFunc[0](path, isDir) {
							return nil
						}
					}
					// if !isDir {
					// 	path.Println("[+]")
					// }
					pathNew := path.Replace(srcBase.Str(), dstDir.Str())
					// path.Println("sp")
					// pathNew.Println("np")
					if isDir {
						pathNew.Mkdir()
					} else {
						err := path.Cp(pathNew.Str(), filterFunc...)
						if err != nil {

							Str("cp file err:" + err.Error()).Color("r").Println(str)
						}
					}
					return nil
				})
				return nil
			} else {
				// log.Fatal("err:")
				return errors.New("can not mkdir path:" + newpath)
			}

		}
	} else {
		return errors.New(("str is not file path" + str).String())
	}
}

func (str Str) Mv(newpath string, filterFunc ...func(path Str, isDir bool) bool) error {
	if err := str.Cp(newpath, filterFunc...); err != nil {
		return err
	} else {
		str.Rm()
		return nil
	}
}

func (str Str) Rm() error {
	if str.IsExists() {
		return os.RemoveAll(str.Str())
	} else {
		return errors.New("not a path:" + str.Str())
	}
}

func (str Str) Ls() (ls Strs) {
	ds, err := ioutil.ReadDir(str.String())
	if err != nil {
		panic(err)
	}
	for _, d := range ds {
		ls = ls.Add(str.PathJoin(d.Name()))
	}
	return
}
